/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.agera.database;

import static com.google.agera.Preconditions.checkNotNull;
import static com.google.agera.Result.failure;
import static com.google.agera.Result.success;

import com.google.agera.*;

import ohos.data.rdb.AbsRdbPredicates;
import ohos.data.rdb.RawRdbPredicates;
import ohos.data.rdb.RdbStore;
import ohos.data.resultset.ResultSet;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Utility methods for obtaining database querying {@link Function} instances.
 */
public final class SqlDatabaseFunctions {
    /**
     * Creates a sql query {@link Function}.
     *
     * @param database Supplier
     * @param rowMap Function
     * @return Function
     */
    @NonNull
    public static <T> Function<SqlRequest, Result<List<T>>> databaseQueryFunction(
            @NonNull final Supplier<Result<RdbStore>> database, @NonNull Function<ResultSet, T> rowMap) {
        return new DatabaseFunction<>(database, new DatabaseQueryMerger<>(rowMap));
    }

    /**
     * Creates a sql insert {@link Function}.
     *
     * @param database Supplier
     * @return Function
     */
    @NonNull
    public static Function<SqlInsertRequest, Result<Long>> databaseInsertFunction(
            @NonNull final Supplier<Result<RdbStore>> database) {
        return new DatabaseFunction<>(database, new DatabaseInsertMerger());
    }

    /**
     * Creates a sql update {@link Function}.
     *
     * @param database Supplier
     * @return Function
     */
    @NonNull
    public static Function<SqlUpdateRequest, Result<Integer>> databaseUpdateFunction(
            @NonNull final Supplier<Result<RdbStore>> database) {
        return new DatabaseFunction<>(database, new DatabaseUpdateMerger());
    }

    /**
     * Creates a sql delete {@link Function}.
     *
     * @param database Supplier
     * @return Function
     */
    @NonNull
    public static Function<SqlDeleteRequest, Result<Integer>> databaseDeleteFunction(
            @NonNull final Supplier<Result<RdbStore>> database) {
        return new DatabaseFunction<>(database, new DatabaseDeleteMerger());
    }

    private static final class DatabaseInsertMerger implements Merger<RdbStore, SqlInsertRequest, Result<Long>> {
        @NonNull
        @Override
        public Result<Long> merge(@NonNull final RdbStore database, @NonNull final SqlInsertRequest input) {
            try {
                return success(
                        database.insertWithConflictResolution(
                                input.table, input.contentValues, input.conflictAlgorithm));
            } catch (final Exception e) {
                return failure(e);
            }
        }
    }

    private static final class DatabaseUpdateMerger implements Merger<RdbStore, SqlUpdateRequest, Result<Integer>> {
        @NonNull
        @Override
        public Result<Integer> merge(@NonNull final RdbStore database, @NonNull final SqlUpdateRequest input) {
            try {
                AbsRdbPredicates rdbPredicates = new RawRdbPredicates(input.table, input.where, input.arguments);
                return success(
                        database.updateWithConflictResolution(
                                input.contentValues, rdbPredicates, input.conflictAlgorithm));
            } catch (final Exception e) {
                return failure(e);
            }
        }
    }

    private static final class DatabaseDeleteMerger implements Merger<RdbStore, SqlDeleteRequest, Result<Integer>> {
        @NonNull
        @Override
        public Result<Integer> merge(@NonNull final RdbStore database, @NonNull final SqlDeleteRequest input) {
            try {
                AbsRdbPredicates rdbPredicates = new RawRdbPredicates(input.table, input.where, input.arguments);
                return success(database.delete(rdbPredicates));
            } catch (final Exception e) {
                return failure(e);
            }
        }
    }

    private static final class DatabaseQueryMerger<T> implements Merger<RdbStore, SqlRequest, Result<List<T>>> {
        @NonNull
        private final Function<ResultSet, T> cursorToItem;

        private DatabaseQueryMerger(@NonNull final Function<ResultSet, T> cursorToItem) {
            this.cursorToItem = checkNotNull(cursorToItem);
        }

        @NonNull
        @Override
        public Result<List<T>> merge(@NonNull final RdbStore database, @NonNull final SqlRequest input) {
            try {
                final ResultSet cursor = database.querySql(input.sql, input.arguments);
                try {
                    final int count = cursor.getRowCount();
                    if (count == 0) {
                        return success(Collections.<T>emptyList());
                    }
                    final List<T> items = new ArrayList<>(count);
                    while (cursor.goToNextRow()) {
                        items.add(cursorToItem.apply(cursor));
                    }
                    return success(items);
                } finally {
                    cursor.close();
                }
            } catch (Exception e) {
                return failure(e);
            }
        }
    }

    static final class DatabaseFunction<R, T> implements Function<R, Result<T>> {
        @NonNull
        private final Supplier<Result<RdbStore>> databaseSupplier;

        @NonNull
        private final Merger<RdbStore, R, Result<T>> databaseWithSqlArgument;

        DatabaseFunction(
                @NonNull final Supplier<Result<RdbStore>> databaseSupplier,
                @NonNull final Merger<RdbStore, R, Result<T>> databaseWithSqlArgumentMerger) {
            this.databaseSupplier = checkNotNull(databaseSupplier);
            this.databaseWithSqlArgument = checkNotNull(databaseWithSqlArgumentMerger);
        }

        @NonNull
        @Override
        public Result<T> apply(@NonNull final R sqlArguments) {
            return databaseSupplier.get().ifSucceededAttemptMerge(sqlArguments, databaseWithSqlArgument);
        }
    }

    private SqlDatabaseFunctions() {}
}
